<?php
namespace CongoSSO;

use DateTime;
use UnexpectedValueException;

class BeforeValidException extends UnexpectedValueException {
}

class ExpiredException extends UnexpectedValueException {
}

class SignatureInvalidException extends UnexpectedValueException {
}

class JWT {
    private static $header = array(
        'typ'=>'JWT',    //类型
        'alg'=>'HS256', //生成signature的算法
    );

    /**
     * 获取jwt token
     * @param array $payload jwt载荷   格式如下非必须
     * [
     *  'iss'=>'jwt_admin',  //该JWT的签发者
     *  'iat'=>time(),  //签发时间
     *  'exp'=>time()+7200,  //过期时间
     *  'nbf'=>time()+60,  //该时间之前不接收处理该Token
     *  'sub'=>'www.admin.com',  //面向的用户
     *  'jti'=>md5(uniqid('JWT').time())  //该Token唯一标识
     * ]
     * @return bool|string
     */
    public static function encode(array $payload, string $key)
    {
        if (is_array($payload)) {
            $base64header = self::base64UrlEncode(json_encode(self::$header));
            $base64payload = self::base64UrlEncode(json_encode($payload));

            $decodeKey = base64_decode($key);
            $signature = self::signature($base64header . "." . $base64payload, $decodeKey, self::$header['alg']);
            $token = $base64header . '.' . $base64payload . '.' . $signature;
            return $token;
        } else {
            return false;
        }
    }


    /**
     * 验证token是否有效,默认验证exp,nbf,iat时间
     * @param string $Token 需要验证的token
     * @return bool|string
     */
    public static function decode(string $token, string $key)
    {
        $tokens = explode('.', $token);
        if (count($tokens) != 3) {
            throw new UnexpectedValueException('Wrong number of segments');
        }

        list($base64header, $base64payload, $sign) = $tokens;

        //获取jwt算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);

        if (empty($base64decodeheader['alg'])) {
            throw new UnexpectedValueException('Invalid header encoding');
        }

        //签名验证
        $decodeKey = base64_decode($key);
        if (self::signature($base64header . '.' . $base64payload, $decodeKey, $base64decodeheader['alg']) !== $sign) {
            throw new SignatureInvalidException('Invalid signature encoding');
        }

        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

        //签发时间大于当前服务器时间验证失败，允许服务器有一分钟的误差
        if (isset($payload['iat']) && $payload['iat'] > time() + 60) {
            throw new BeforeValidException(
                'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload['iat'])
            );
        }

        //过期时间小宇当前服务器时间验证失败
        if (isset($payload['exp']) && $payload['exp'] < time()) {
            throw new ExpiredException('Expired token');
        }

        //该nbf时间之前不接收处理该Token
        if (isset($payload['nbf']) && $payload['nbf'] > time()) {
            throw new BeforeValidException(
                'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload['nbf'])
            );
        }

        return $payload;
    }

    private static function base64UrlEncode(string $input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    private static function base64UrlDecode(string $input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    private static function signature(string $input, string $key, string $alg = 'HS256')
    {
        $alg_config = array('HS256'=>'sha256');
        return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key, true));
    }
}
